同步和锁

在多线程编程中,同步和锁是确保线程安全的关键概念。当多个线程同时访问共享资源时,可能会导致数据不一致或竞态条件。为了解决这些问题,Java 提供了多种同步机制和锁机制。

1. 同步机制

1.1 同步方法

同步方法是最简单的同步机制之一。通过在方法声明中使用 synchronized 关键字,可以确保同一时间只有一个线程可以执行该方法。

public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

在这个例子中,increment 和 getCount 方法都是同步的,因此同一时间只有一个线程可以执行这些方法。

1.2 同步块

同步块允许你更细粒度地控制同步的范围。你可以选择一个对象作为锁,并在同步块中使用该对象来保护共享资源。

public class SynchronizedBlockExample {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

在这个例子中,lock 对象用于同步 increment 和 getCount 方法中的代码块。

2. 锁机制

2.1 ReentrantLock

ReentrantLock 是 Java 提供的一种可重入锁。它比内置的 synchronized 关键字更灵活,支持更多的功能,如公平锁、可中断锁等。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

在这个例子中,ReentrantLock 用于保护 increment 和 getCount 方法中的代码块。注意,unlock 操作必须在 finally 块中执行,以确保锁在任何情况下都能被释放。

2.2 ReadWriteLock

ReadWriteLock 允许多个线程同时读取共享资源,但在写操作时需要独占访问。这种锁机制适用于读多写少的场景。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private int count = 0;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public void increment() {
        lock.writeLock().lock();
        try {
            count++;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getCount() {
        lock.readLock().lock();
        try {
            return count;
        } finally {
            lock.readLock().unlock();
        }
    }
}

在这个例子中,ReadWriteLock 用于区分读操作和写操作。多个线程可以同时读取 count,但在写操作时需要独占访问。

3. 实践练习

3.1 同步方法练习

编写一个程序,创建两个线程,每个线程调用同一个对象的同步方法 increment 1000 次。最后打印 count 的值,确保它是 2000。

public class SynchronizedMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        SynchronizedExample example = new SynchronizedExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final count: " + example.getCount());
    }
}

3.2 ReentrantLock 练习

编写一个程序,使用 ReentrantLock 实现与上面相同的逻辑。

public class ReentrantLockExercise {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockExample example = new ReentrantLockExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final count: " + example.getCount());
    }
}

3.3 ReadWriteLock 练习

编写一个程序,使用 ReadWriteLock 实现一个简单的缓存。缓存支持多个线程同时读取,但在写入时需要独占访问。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExercise {
    private final Map<String, String> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public String get(String key) {
        lock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void put(String key, String value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockExercise cache = new ReadWriteLockExercise();

        Thread writer = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                cache.put("key" + i, "value" + i);
            }
        });

        Thread reader = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(cache.get("key" + i));
            }
        });

        writer.start();
        reader.start();

        writer.join();
        reader.join();
    }
}

4. 总结

同步和锁是多线程编程中的重要概念。通过使用 synchronized 关键字、ReentrantLock 和 ReadWriteLock,你可以有效地控制线程对共享资源的访问,避免数据不一致和竞态条件。

在实际开发中,选择合适的同步机制和锁机制取决于具体的应用场景。希望本教程能帮助你更好地理解和应用这些概念。